Note: This tutorial is under-construction and does not represent the final product..
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Getting Started With Descartes

Description: An introductory tutorial to the Descartes cartesian path-planner.

Keywords: Descartes, descartes, path planner

Tutorial Level: BEGINNER

Overview

In this tutorial, we will be using the Descartes path planner to generate a robot joint trajectory for a given Cartesian path. Source code for this tutorial can currently be found at this website.

Install Descartes

(RECOMMENDED) Binary Install

NOTE: Recommended, but binary is not ready for ROS Indigo (release requested). Following will become available once the request is fulfilled.

sudo apt-get install ros-indigo-descartes

(Optional) Source Install

For Ubuntu:

sudo apt-get install python-catkin-tools python-rosdep
cd %YOUR_CATKIN_WS%/src
git clone https://github.com/ros-industrial-consortium/descartes.git
cd cd %YOUR_CATKIN_WS%
rosdep install -r -y --from-paths src --ignore-src
catkin build
source devel/setup.bash

Using Descartes

Using Descartes requires a software developer to create three objects:

  1. A robot model which will solve forward and inverse kinematics.

  2. A trajectory of trajectory points to define what the path looks like.

  3. A planner that will do the work of finding a valid route along the trajectory using the provided model of the robot.

Source Code

What follows is a single file program that defines a simple trajectory, creates a robot model using Moveit, plans for the trajectory, and executes it. At the top are several function declarations the source code for which is detailed in a following section.

   1 // Core ros functionality like ros::init and spin
   2 #include <ros/ros.h>
   3 // ROS Trajectory Action server definition
   4 #include <control_msgs/FollowJointTrajectoryAction.h>
   5 // Means by which we communicate with above action-server
   6 #include <actionlib/client/simple_action_client.h>
   7 
   8 // Includes the descartes robot model we will be using
   9 #include <descartes_moveit/moveit_state_adapter.h>
  10 // Includes the descartes trajectory type we will be using
  11 #include <descartes_trajectory/axial_symmetric_pt.h>
  12 #include <descartes_trajectory/cart_trajectory_pt.h>
  13 // Includes the planner we will be using
  14 #include <descartes_planner/dense_planner.h>
  15 
  16 typedef std::vector<descartes_core::TrajectoryPtPtr> TrajectoryVec;
  17 typedef TrajectoryVec::const_iterator TrajectoryIter;
  18 
  19 /**
  20  * Generates an completely defined (zero-tolerance) cartesian point from a pose
  21  */
  22 descartes_core::TrajectoryPtPtr
  23 makeCartesianPoint(const Eigen::Affine3d& pose);
  24 
  25 /**
  26  * Generates a cartesian point with free rotation about the Z axis of the EFF frame
  27  */
  28 descartes_core::TrajectoryPtPtr
  29 makeTolerancedCartesianPoint(const Eigen::Affine3d& pose);
  30 
  31 /**
  32  * Translates a descartes trajectory to a ROS joint trajectory
  33  */
  34 trajectory_msgs::JointTrajectory
  35 toROSJointTrajectory(const TrajectoryVec& trajectory,
  36                      const descartes_core::RobotModel& model,
  37                      const std::vector<std::string>& joint_names, double time_delay);
  38 
  39 /**
  40  * Sends a ROS trajectory to the robot controller
  41  */
  42 bool executeTrajectory(const trajectory_msgs::JointTrajectory& trajectory);
  43 
  44 int main(int argc, char** argv)
  45 {
  46   // Initialize ROS
  47   ros::init(argc, argv, "descartes_tutorial");
  48   ros::NodeHandle nh;
  49 
  50   // Required for communication with moveit components
  51   ros::AsyncSpinner spinner (1);
  52   spinner.start();
  53 
  54   // 1. Define sequence of points
  55   TrajectoryVec points;
  56   for (unsigned int i = 0; i < 10; ++i)
  57   {
  58     Eigen::Affine3d pose;
  59     pose = Eigen::Translation3d(0.0, 0.0, 1.0 + 0.05 * i);
  60     descartes_core::TrajectoryPtPtr pt = makeTolerancedCartesianPoint(pose);
  61     points.push_back(pt);
  62   }
  63 
  64   for (unsigned int i = 0; i < 5; ++i)
  65   {
  66     Eigen::Affine3d pose;
  67     pose = Eigen::Translation3d(0.0, 0.04 * i, 1.3);
  68     descartes_core::TrajectoryPtPtr pt = makeTolerancedCartesianPoint(pose);
  69     points.push_back(pt);
  70   }
  71 
  72   // 2. Create a robot model and initialize it
  73   descartes_core::RobotModelPtr model (new descartes_moveit::MoveitStateAdapter);
  74 
  75   // Name of description on parameter server. Typically just "robot_description".
  76   const std::string robot_description = "robot_description";
  77 
  78   // name of the kinematic group you defined when running MoveitSetupAssistant
  79   const std::string group_name = "manipulator";
  80 
  81   // Name of frame in which you are expressing poses.
  82   // Typically "world_frame" or "base_link".
  83   const std::string world_frame = "base_link";
  84 
  85   // tool center point frame (name of link associated with tool)
  86   const std::string tcp_frame = "tool0";
  87 
  88   if (!model->initialize(robot_description, group_name, world_frame, tcp_frame))
  89   {
  90     ROS_INFO("Could not initialize robot model");
  91     return -1;
  92   }
  93 
  94   // 3. Create a planner and initialize it with our robot model
  95   descartes_planner::DensePlanner planner;
  96   planner.initialize(model);
  97 
  98   // 4. Feed the trajectory to the planner
  99   if (!planner.planPath(points))
 100   {
 101     ROS_ERROR("Could not solve for a valid path");
 102     return -2;
 103   }
 104 
 105   TrajectoryVec result;
 106   if (!planner.getPath(result))
 107   {
 108     ROS_ERROR("Could not retrieve path");
 109     return -3;
 110   }
 111 
 112   // 5. Translate the result into a type that ROS understands
 113   // Get Joint Names
 114   std::vector<std::string> names;
 115   nh.getParam("controller_joint_names", names);
 116   // Generate a ROS joint trajectory with the result path, robot model,
 117   // joint names, and a certain time delta between each trajectory point
 118   trajectory_msgs::JointTrajectory joint_solution =
 119       toROSJointTrajectory(result, *model, names, 1.0);
 120 
 121   // 6. Send the ROS trajectory to the robot for execution
 122   if (!executeTrajectory(joint_solution))
 123   {
 124     ROS_ERROR("Could not execute trajectory!");
 125     return -4;
 126   }
 127 
 128   // Wait till user kills the process (Control-C)
 129   ROS_INFO("Done!");
 130   return 0;
 131 }

Discussion

   1 // Core ros functionality like ros::init and spin
   2 #include <ros/ros.h>
   3 // ROS Trajectory Action server definition
   4 #include <control_msgs/FollowJointTrajectoryAction.h>
   5 // Means by which we communicate with above action-server
   6 #include <actionlib/client/simple_action_client.h>
   7 

The first section of headers includes fundamental ROS components needed for printing, defining trajectories, and sending those trajectories. Most robots supported by ROS-Industrial offer a FollowJointTrajectory action interface for both simulation and actual hardware.

   8 // Includes the descartes robot model we will be using
   9 #include <descartes_moveit/moveit_state_adapter.h>
  10 // Includes the descartes trajectory type we will be using
  11 #include <descartes_trajectory/axial_symmetric_pt.h>
  12 #include <descartes_trajectory/cart_trajectory_pt.h>
  13 // Includes the planner we will be using
  14 

This second batch of headers includes all of the descartes specific files we'll need to generate trajectories. moveit_state_adapter.h defines a descartes RobotModel while the two files from descartes_trajectory define specific types of Trajectory Points. Finally dense_planner.h provides access to a descartes Planner. See descartes for more information about the specific components.

  46 {
  47   // Initialize ROS
  48   ros::init(argc, argv, "descartes_tutorial");
  49   ros::NodeHandle nh;
  50 
  51   // Required for communication with moveit components
  52   ros::AsyncSpinner spinner (1);

Before we can use ROS components, we must initialize our node by calling ros::init. I also create a NodeHandle here which will be used to retrieve components from the ROS parameter server later.

Lines 51-52 create a new thread to handle any callbacks that might need processing while we're executing descartes. Initializing MoveIt without the spinner seemed to cause problems.

  54   // 1. Define sequence of points
  55   TrajectoryVec points;
  56   for (unsigned int i = 0; i < 10; ++i)
  57   {
  58     Eigen::Affine3d pose;
  59     pose = Eigen::Translation3d(0.0, 0.0, 1.0 + 0.05 * i);
  60     descartes_core::TrajectoryPtPtr pt = makeTolerancedCartesianPoint(pose);
  61     points.push_back(pt);
  62   }
  63 
  64   for (unsigned int i = 0; i < 5; ++i)
  65   {
  66     Eigen::Affine3d pose;
  67     pose = Eigen::Translation3d(0.0, 0.04 * i, 1.3);
  68     descartes_core::TrajectoryPtPtr pt = makeTolerancedCartesianPoint(pose);
  69     points.push_back(pt);

Here we define a series of trajectory points using Eigen transformations. These transformations define an upward motion of the end effector followed by a sideways translation.

The makeTolerancedCartesianPoint function is a helper function for generating Cartesian points whose rotation about the z-axis of the end effector frame is unconstrained. When solving the graph, Descartes will attempt to search for valid IK solutions by essentially rotating the target pose around the z-axis.

The definition of makeTolerancedCartesianPoint is straight forward and will be discussed shortly.

  72   // 2. Create a robot model and initialize it
  73   descartes_core::RobotModelPtr model (new descartes_moveit::MoveitStateAdapter);
  74 
  75   // Name of description on parameter server. Typically just "robot_description".
  76   const std::string robot_description = "robot_description";
  77 
  78   // name of the kinematic group you defined when running MoveitSetupAssistant
  79   const std::string group_name = "manipulator";
  80 
  81   // Name of frame in which you are expressing poses.
  82   // Typically "world_frame" or "base_link".
  83   const std::string world_frame = "base_link";
  84 
  85   // tool center point frame (name of link associated with tool)
  86   const std::string tcp_frame = "tool0";
  87 
  88   if (!model->initialize(robot_description, group_name, world_frame, tcp_frame))
  89   {
  90     ROS_INFO("Could not initialize robot model");
  91     return -1;

In this block of code, we create and initialize a robot model by providing information about the robot we want to control. A prerequisite for using the provided moveit_state_adapter is that one must have run the Moveit Setup Assistant and created at least one kinematic groups prior to running this sample.

Underneath the hood, the code searches the ROS parameter server for a description of the robot (or system containing the robot) from the robot_description parameter. The group_name parameter should match the name of the kinematic group you created when running Moveit Setup Assistant. The default ROS-Industrial packages typically use 'manipulator' as their default group name.

The world_frame parameter specifies the frame in which the poses of positions you pass in are expressed. If you are specifying global positions relative to some predetermined origin, then the name of that origin is the argument to world_frame (it's also the root frame in your URDF). If the points are expressed relative to the robot base or some other fixed frame, you pass in the name of the base link or any other frame you'd like to use.

The tcp_frame, or tool center point frame, is the frame of reference used when calculating forward and inverse kinematics. It will almost always be the last link in your manipulator group (associate with the name group_name).

  94   // 3. Create a planner and initialize it with our robot model
  95   descartes_planner::DensePlanner planner;
  96   planner.initialize(model);
  97 
  98   // 4. Feed the trajectory to the planner
  99   if (!planner.planPath(points))
 100   {
 101     ROS_ERROR("Could not solve for a valid path");
 102     return -2;
 103   }
 104 
 105   TrajectoryVec result;
 106   if (!planner.getPath(result))
 107   {
 108     ROS_ERROR("Could not retrieve path");
 109     return -3;

Once you have defined a Robot Model and a trajectory, planning is straightforward. Create the planner of your choice, initialize it with the model that was just created (and initialized itself), call the planPath function while passing in the desired trajectory. This function call does the bulk of the calculations to generate appropriate joint trajectories and could take a while to return depending on the complexity of the plan being analyzed.

The getPath function is used to retrieve the resulting trajectory. To make the planners as generic as possible, it returns the polymorphic type TrajectoryPtPtr, but for both Dense Planner and Sparse Planner the underlying type is JointTrajectoryPt. The joint values themselves are unpacked later in the toROSJointTrajectory function which will be discussed shortly.

Be sure to check that the return value of both the planPath and getPath functions is true as they can fail for a variety of reasons including unreachable points and non-smooth joint trajectories.

 112   // 5. Translate the result into a type that ROS understands
 113   // Get Joint Names
 114   std::vector<std::string> names;
 115   nh.getParam("controller_joint_names", names);
 116   // Generate a ROS joint trajectory with the result path, robot model,
 117   // joint names, and a certain time delta between each trajectory point
 118   trajectory_msgs::JointTrajectory joint_solution =
 119       toROSJointTrajectory(result, *model, names, 1.0);
 120 
 121   // 6. Send the ROS trajectory to the robot for execution
 122   if (!executeTrajectory(joint_solution))
 123   {
 124     ROS_ERROR("Could not execute trajectory!");
 125     return -4;

This final code block converts the joint trajectory generated by Descartes to a format that ROS understands. The toROSJointTrajectory helper function performs this task, but requires a few pieces of information:

  1. The descartes joint trajectory solutions
  2. The descartes robot model used to interpret those points
  3. The names of the joints being actively commanded
  4. The time between each joint position

Lines 114-115 retrieve the joint names of the robot in the tutorial from the ROS parameter server.

Helper Functions

   1 descartes_core::TrajectoryPtPtr makeTolerancedCartesianPoint(const Eigen::Affine3d& pose)
   2 {
   3   using namespace descartes_core;
   4   using namespace descartes_trajectory;
   5   return TrajectoryPtPtr( new AxialSymmetricPt(pose, // Nominal pose
   6                                                M_PI/2.0, // Search discretization
   7                                                AxialSymmetricPt::Z_AXIS) ); // Free axis
   8 }

makeTolerancedCartesianPoint is a small wrapper function around Descartes' AxialSymmetricPt class. This class is itself a specialization of a Cartesian point where the final argument (here AxialSymmetricPt::Z_AXIS) is the axis of the nominal pose about which the tool can rotate freely. Other options include X_AXIS and Y_AXIS.

If you wanted to create a fixed 6D cartesian pose, or if you wanted greater control over the acceptable tolerances, you can create a Cartesian point with equivalent properties.

TrajectoryPtPtr is an alias for a boost::shared_ptr with type descartes_core::TrajectoryPt. Shared pointers are a means of automatically managing object lifetime and can, in many cases, be used just as a normal pointer would be.

   1 trajectory_msgs::JointTrajectory
   2 toROSJointTrajectory(const TrajectoryVec& trajectory,
   3                      const descartes_core::RobotModel& model,
   4                      const std::vector<std::string>& joint_names,
   5                      double time_delay)
   6 {
   7   // Fill out information about our trajectory
   8   trajectory_msgs::JointTrajectory result;
   9   result.header.stamp = ros::Time::now();
  10   result.header.frame_id = "world_frame";
  11   result.joint_names = joint_names;
  12 
  13   // For keeping track of time-so-far in the trajectory
  14   double time_offset = 0.0;
  15   // Loop through the trajectory
  16   for (TrajectoryIter it = trajectory.begin(); it != trajectory.end(); ++it)
  17   {
  18     // Find nominal joint solution at this point
  19     std::vector<double> joints;
  20     it->get()->getNominalJointPose(std::vector<double>(), model, joints);
  21 
  22     // Fill out a ROS trajectory point
  23     trajectory_msgs::JointTrajectoryPoint pt;
  24     pt.positions = joints;
  25     // velocity, acceleration, and effort are given dummy values
  26     // we'll let the controller figure them out
  27     pt.velocities.resize(joints.size(), 0.0);
  28     pt.accelerations.resize(joints.size(), 0.0);
  29     pt.effort.resize(joints.size(), 0.0);
  30     // set the time into the trajectory
  31     pt.time_from_start = ros::Duration(time_offset);
  32     // increment time
  33     time_offset += time_delay;
  34 
  35     result.points.push_back(pt);
  36   }
  37 
  38   return result;
  39 }

This function is responsible for converting a descartes trajectory into a ROS trajectory_msgs::JointTrajectory. As with any ROS message type, be sure to fill in the header information including frame_id and time-stamp.

Descartes' planners return a vector of trajectory point pointers, so we must access the underlying data polymorphically. The underlying type returned by both the sparse and dense planner is a joint trajectory point. The getNominalJointPose function can be used to extract the desired joint positions. For this type of point, the first argument to the function is just a dummy one. Next, we resize the velocity, acceleration, and effort fields and set all values to zero so that the controller will not reject our points and will hopefully figure out these values on its own. Finally we keep a running count of the time so far in the trajectory. ROS trajectory point timings are specified relative to the start of the trajectory, not the previous point.

   1 bool executeTrajectory(const trajectory_msgs::JointTrajectory& trajectory)
   2 {
   3   // Create a Follow Joint Trajectory Action Client
   4   actionlib::SimpleActionClient<control_msgs::FollowJointTrajectoryAction> ac("joint_trajectory_action", true);
   5   if (!ac.waitForServer(ros::Duration(2.0)))
   6   {
   7     ROS_ERROR("Could not connect to action server");
   8     return false;
   9   }
  10 
  11   control_msgs::FollowJointTrajectoryGoal goal;
  12   goal.trajectory = trajectory;
  13   goal.goal_time_tolerance = ros::Duration(1.0);
  14   
  15   ac.sendGoal(goal);
  16 
  17   if (ac.waitForResult(goal.trajectory.points[goal.trajectory.points.size()-1].time_from_start + ros::Duration(5)))
  18   {
  19     ROS_INFO("Action server reported successful execution");
  20     return true;
  21   } else {
  22     ROS_WARN("Action server could not execute trajectory");
  23     return false;
  24   }
  25 }

Build

CMakeLists.txt in descartes_tutorials repository shows a minimal sample for the code above. Add necessary config to your CMakeLists.txt, then build by catkin_make etc.

Running the Sample

To run the code, first make sure you've built the source code by changing to your workspaces home directory and running catkin_make.

Running the node you created in the tutorial first requires you to launch moveit for your desired robot. Included in the tutorial package structure is a launch file, motoman_sim.launch which will launch moveit and a simulator for a Motoman SIA20D robot. Alternatively, one can launch the moveit_planning_execution.launch file with the sim:=true argument for many ROS-Industrial robots including Fanuc, ABB, and other Motoman robots.

In summary:

roslaunch descartes_tutorials motoman_sim.launch

and in a separate (sourced) terminal:

roslaunch descartes_tutorials tutorial1.launch

Wiki: descartes/Tutorials/Getting Started with Descartes (last edited 2017-03-31 20:13:23 by IsaacSaito)